"""Hyperbola geometrical entities.

Contains
* Hyperbola

"""

from sympy.core.expr import Expr
from sympy.core.relational import Eq
from sympy.core import S, pi, sympify
from sympy.core.evalf import N
from sympy.core.parameters import global_parameters
from sympy.core.logic import fuzzy_bool
from sympy.core.numbers import Rational, oo, zoo
from sympy.core.sorting import ordered
from sympy.core.symbol import Dummy, uniquely_named_symbol, _symbol
from sympy.simplify import simplify, trigsimp
from sympy.functions.elementary.miscellaneous import sqrt, Max
from sympy.functions.elementary.trigonometric import cos, sin, tan
from sympy.functions.special.elliptic_integrals import elliptic_e
from sympy.geometry.entity import GeometryEntity, GeometrySet
from sympy.geometry.exceptions import GeometryError
from sympy.geometry.line import Line, Segment, Ray2D, Segment2D, Line2D, LinearEntity3D
from sympy.geometry.point import Point, Point2D, Point3D
from sympy.geometry.polygon import Polygon, Triangle
from sympy.geometry import Parabola, Ellipse
from sympy.geometry.util import idiff, find
from sympy.polys import DomainError, Poly, PolynomialError
from sympy.polys.polyutils import _not_a_coeff, _nsort
from sympy.solvers import solve
from sympy.solvers.solveset import nonlinsolve,linear_coeffs
from sympy.utilities.misc import filldedent, func_name

from mpmath.libmp.libmpf import prec_to_dps

import random

x, y = [Dummy('hyperbola_dummy', real=True) for i in range(2)]


class Hyperbola(GeometrySet):
    """An hyperbolic GeometryEntity.

    Parameters
    ==========

    center : Point, optional
        Default value is Point(0, 0)
    real : number or SymPy expression, optional
    imaginary : number or SymPy expression, optional
    eccentricity : number or SymPy expression, optional
        Two of `real`, `imaginary` and `eccentricity` must be supplied to
        create an Hyperbola. The third is derived from the two supplied.
    slope : number or SymPy expression, optional
        if the hyperbola's real radius is not on the x-axis,the slope should use.

    Attributes
    ==========

    center
    real
    imaginary
    circumference
    eccentricity
    periapsis
    apoapsis
    focus_distance
    foci

    Raises
    ======

    GeometryError
        When `real`, `imaginary` and `eccentricity` are incorrectly supplied
        as parameters.
    TypeError
        When `center` is not a Point.

    Notes
    -----
    Constructed from a center and two radii, the first being the horizontal
    radius (along the x-axis) and the second being the vertical radius (along
    the y-axis).

    When symbolic value for real and imaginary are used, any calculation that
    refers to the foci or the real or imaginary axis will assume that the hyperbola
    has its real radius on the x-axis. If this is not true then the slope is necessary.

    Examples
    ========

    >>> from sympy import Hyperbola, Point, Rational
    >>> e1 = Hyperbola(Point(0, 0), 5, 1)
    >>> e1.real, e1.imaginary
    (5, 1)
    >>> e2 = Hyperbola(Point(3, 1), real=3, eccentricity=Rational(5, 3))
    >>> e2
    Hyperbola(Point2D(3, 1), 3, 4, 0)

    """

    def __contains__(self, o):
        if isinstance(o, Point):
            res = self.equation(x, y).subs({x: o.x, y: o.y})
            return trigsimp(simplify(res)) is S.Zero
        elif isinstance(o, Hyperbola):
            return self == o
        return False

    def __eq__(self, o):
        """Is the other GeometryEntity the same as this hyperbola?"""
        return isinstance(o, Hyperbola) and (self.center == o.center and
                                           self.real == o.real and
                                           self.imaginary == o.imaginary and self.slope == o.slope)

    def __hash__(self):
        return super().__hash__()

    def __new__(
        cls, center=None, real=None, imaginary=None, eccentricity=None, slope=None, **kwargs):

        real = sympify(real)
        imaginary = sympify(imaginary)
        if slope is None:
            slope=0
        else:
          slope=trigsimp(slope)

        if center is None:
            center = Point(0, 0)
        else:
            if len(center) != 2:
                raise ValueError('The center of "{}" must be a two dimensional point'.format(cls))
            center = Point(center, dim=2)

        if len(list(filter(lambda x: x is not None, (real, imaginary, eccentricity)))) != 2:
            raise ValueError(filldedent('''
                Exactly two arguments of "real", "imaginary", and
                "eccentricity" must not be None.'''))

        if eccentricity is not None:
            eccentricity = sympify(eccentricity)
            if eccentricity.is_negative:
                raise GeometryError("Eccentricity of hyperbola/circle should lie between [0, 1)")
            elif real is None:
                real = imaginary / sqrt(eccentricity**2-1)
            elif imaginary is None:
                imaginary = real * sqrt(eccentricity**2-1)

        if S.Zero in (real, imaginary):
            return Line(Point(center[0] - real, center[1] - imaginary), Point(center[0] + real, center[1] + imaginary))

        if real.is_real is False or imaginary.is_real is False:
            raise GeometryError("Invalid value encountered when computing real / imaginary.")

        return GeometryEntity.__new__(cls, center, real, imaginary, slope, **kwargs)

    def _svg(self, scale_factor=1., fill_color="#66cc99"):
        """Returns SVG hyperbola element for the Hyperbola.

        Parameters
        ==========

        scale_factor : float
            Multiplication factor for the SVG stroke-width.  Default is 1.
        fill_color : str, optional
            Hex string for fill color. Default is "#66cc99".
        """

        c = N(self.center)
        h, v = N(self.real), N(self.imaginary)
        return (
            '<hyperbola fill="{1}" stroke="#555555" '
            'stroke-width="{0}" opacity="0.6" cx="{2}" cy="{3}" rx="{4}" ry="{5}"/>'
        ).format(2. * scale_factor, fill_color, c.x, c.y, h, v)

    @property
    def ambient_dimension(self):
        return 2

    @property
    def apoapsis(self):
        """The apoapsis of the hyperbola.

        The shortest distance between the focus and the longer contour.

        Returns
        =======

        apoapsis : number or SymPy expression

        See Also
        ========

        periapsis : Returns shortest distance between foci and the lear contour

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.apoapsis
        3 + sqrt(10) 

        """
        return self.real * (1 + self.eccentricity)

    def arbitrary_point(self, parameter='t'):
        """A parameterized point on the hyperbola.

        Parameters
        ==========

        parameter : str, optional
            Default value is 't'.

        Returns
        =======

        arbitrary_point : Point

        Raises
        ======

        ValueError
            When `parameter` already appears in the functions.

        See Also
        ========

        sympy.geometry.point.Point

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> e1 = Hyperbola(Point(0, 0), 3, 2)
        >>> e1.arbitrary_point()
        Point2D(3/cos(t), 2*tan(t))

        """
        t = _symbol(parameter, real=True)
        if t.name in (f.name for f in self.free_symbols):
            raise ValueError(filldedent('Symbol %s already appears in object '
                                        'and cannot be used as a parameter.' % t.name))
        cx = self.center.x
        cy = self.center.y
        if self.slope == oo or self.slope == -oo:
            return Point(cx + self.imaginary*tan(t),cy + self.real/cos(t))
        else:   
            sqrt_h=sqrt(trigsimp(1+self.slope**2))
            return Point(cx + trigsimp(self.real/cos(t)-self.imaginary*tan(t)*self.slope)/sqrt_h,
                        cy + trigsimp(self.real*self.slope/cos(t)+self.imaginary*tan(t))/sqrt_h)

    @property
    def center(self):
        """The center of the hyperbola.

        Returns
        =======

        center : Point

        See Also
        ========

        sympy.geometry.point.Point

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.center
        Point2D(0, 0)

        """
        return self.args[0]

    @property
    def eccentricity(self):
        """The eccentricity of the hyperbola.

        Returns
        =======

        eccentricity : number

        Examples
        ========

        >>> from sympy import Point, Hyperbola, sqrt
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, sqrt(2))
        >>> e1.eccentricity
        sqrt(11)/3

        """
        return self.focus_distance / self.real

    def encloses_point(self, p):
        """
        Return True if p is enclosed by (is inside of) self.

        Notes
        -----
        Being on the border of self is considered False.

        Parameters
        ==========

        p : Point

        Returns
        =======

        encloses_point : True, False or None

        See Also
        ========

        sympy.geometry.point.Point

        Examples
        ========

        >>> from sympy import Hyperbola, S
        >>> from sympy.abc import t
        >>> e = Hyperbola((0, 0), 3, 2)
        >>> e.encloses_point((0, 0))
        False
        >>> e.encloses_point(e.arbitrary_point(t).subs(t, S.Half))
        False
        >>> e.encloses_point((4, 0))
        True

        """
        p = Point(p, dim=2)
        if p in self:
            return False

        # if the minus distance from the foci to p abs(h1 - h2) is less
        # than the  the real axis length then p is inside
        # the hyperbola
        h1, h2 = [f.distance(p) for f in self.foci]
        test = 2*self.real - abs(h1 - h2)

        return not fuzzy_bool(test.is_positive)

    def equation(self, x='x', y='y',_slope=None):
        """
        Returns the equation of an hyperbola aligned with the x and y axes;
        when slope is given, the equation returned corresponds to an hyperbola
        with a real axis having that slope.

        Parameters
        ==========

        x : str, optional
            Label for the x-axis. Default value is 'x'.
        y : str, optional
            Label for the y-axis. Default value is 'y'.
        _slope : Expr, optional
                The slope of the real axis. Ignored when 'None'.

        Returns
        =======

        equation : SymPy expression

        See Also
        ========

        arbitrary_point : Returns parameterized point on hyperbola

        Examples
        ========

        >>> from sympy import Point, Hyperbola, pi
        >>> from sympy.abc import x, y
        >>> e1 = Hyperbola(Point(1, 0), 3, 2)
        >>> eq1 = e1.equation(x, y); eq1
        -y**2/4 + (x - 1)**2/9 - 1
        >>> eq2 = e1.equation(x, y, self.slope=1); eq2
        -(-x + y + 1)**2/8 + (x + y - 1)**2/18 - 1

        A point on e1 satisfies eq1. Let's use one on the x-axis:

        >>> p1 = e1.center + Point(e1.real, 0)
        >>> assert eq1.subs(x, p1.x).subs(y, p1.y) == 0

        When rotated the same as the rotated hyperbola, about the center
        point of the hyperbola, it will satisfy the rotated hyperbola's
        equation, too:

        >>> r1 = p1.rotate(pi/4, e1.center)
        >>> assert eq2.subs(x, r1.x).subs(y, r1.y) == 0

        References
        ==========

        .. [1] https://math.stackexchange.com/questions/108270/what-is-the-equation-of-an-hyperbola-that-is-not-aligned-with-the-axis
        .. [2] https://en.wikipedia.org/wiki/Hyperbola#Shifted_hyperbola

        """

        x = _symbol(x, real=True)
        y = _symbol(y, real=True)

        dx = x - self.center.x
        dy = y - self.center.y

        if (_slope!=None):
            if Eq(1,self.slope*_slope):
                final_slope = oo
            else:
                final_slope=(self.slope+_slope)/(1-self.slope*_slope)
        else:
            final_slope=self.slope

        if final_slope == oo or final_slope == -oo:
            t1=dy**2/self.real**2
            t2=dx**2/self.imaginary**2
            return t1-t2-1
        else:   
            L = (dy - final_slope*dx)**2
            l = (final_slope*dy + dx)**2
            h = 1 + final_slope**2
            b = h*self.real**2
            a = h*self.imaginary**2
            return l/b - L/a - 1

    def evolute(self, x='x', y='y'):
        """The equation of evolute of the hyperbola.

        Parameters
        ==========

        x : str, optional
            Label for the x-axis. Default value is 'x'.
        y : str, optional
            Label for the y-axis. Default value is 'y'.

        Returns
        =======

        equation : SymPy expression

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> e1 = Hyperbola(Point(1, 0), 3, 2)
        >>> e1.evolute()
        2**(2/3)*y**(2/3) + (3*x - 3)**(2/3) - 5**(2/3)
        """
        if len(self.args) != 3:
            raise NotImplementedError('Evolute of arbitrary Hyperbola is not supported.')
        x = _symbol(x, real=True)
        y = _symbol(y, real=True)
        t1 = (self.real*(x - self.center.x))**Rational(2, 3)
        t2 = (self.imaginary*(y - self.center.y))**Rational(2, 3)
        return t1 + t2 - (self.real**2 - self.imaginary**2)**Rational(2, 3)

    @property
    def foci(self):
        """The foci of the hyperbola.

        Notes
        -----
        The foci can only be calculated if the real/imaginary axes are known.

        Raises
        ======

        ValueError
            When the real and imaginary axis cannot be determined.

        See Also
        ========

        sympy.geometry.point.Point
        focus_distance : Returns the distance between focus and center

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.foci
        (Point2D(-sqrt(10), 0), Point2D(sqrt(10), 0))

        """
        c = self.center
        # calculate focus distance manually, since focus_distance calls this
        # routine
        fd = sqrt(self.real**2 + self.imaginary**2)
        if self.slope==oo or self.slope==-oo:
            # foci on the y-axis
            return (c + Point(0, -fd), c + Point(0, fd))
        else:
            h=sqrt(trigsimp(1+self.slope**2))
            fx=(fd)/h
            fy=(fd*self.slope)/h
            return (c + Point(-fx, -fy), c + Point(fx, fy))

    @property
    def focus_distance(self):
        """The focal distance of the hyperbola.

        The distance between the center and one focus.

        Returns
        =======

        focus_distance : number

        See Also
        ========

        foci

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.focus_distance
        sqrt(10)

        """
        return Point.distance(self.center, self.foci[0])

    @property
    def real(self):
        """The real axis of the hyperbola.

        Returns
        =======

        real : number

        See Also
        ========

        imaginary, real, imaginary

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.real
        3

        """
        return self.args[1]

    def intersection(self, o):
        """The intersection of this hyperbola and another geometrical entity
        `o`.

        Parameters
        ==========

        o : GeometryEntity

        Returns
        =======

        intersection : list of GeometryEntity objects

        Notes
        -----
        Currently supports intersections with Point, Line, Segment, Ray,
        Circle and Hyperbola types.

        See Also
        ========

        sympy.geometry.entity.GeometryEntity

        Examples
        ========

        >>> from sympy import Hyperbola, Point, Line
        >>> e = Hyperbola(Point(0, 0), 5, 7)
        >>> e.intersection(Point(0, 0))
        []
        >>> e.intersection(Point(5, 0))
        [Point2D(5, 0)]
        >>> e.intersection(Line(Point(0,0), Point(0, 1)))
        []
        >>> e.intersection(Line(Point(5,0), Point(5, 1)))
        [Point2D(5, 0)]
        >>> e.intersection(Line(Point(6,0), Point(6, 1)))
        []
        >>> e = Hyperbola(Point(-1, 0), 4, 3)
        >>> e.intersection(Hyperbola(Point(1, 0), 4, 3))
        [Point2D(0, -3*sqrt(15)/4), Point2D(0, 3*sqrt(15)/4)]
        >>> e.intersection(Hyperbola(Point(5, 0), 4, 3))
        [Point2D(2, -3*sqrt(7)/4), Point2D(2, 3*sqrt(7)/4)]
        >>> e.intersection(Hyperbola(Point(100500, 0), 4, 3))
        []
        >>> e.intersection(Hyperbola(Point(0, 0), 3, 4))
        [Point2D(3, 0), Point2D(-363/175, -48*sqrt(111)/175), Point2D(-363/175, 48*sqrt(111)/175)]
        >>> e.intersection(Hyperbola(Point(-1, 0), 3, 4))
        [Point2D(-17/5, -12/5), Point2D(-17/5, 12/5), Point2D(7/5, -12/5), Point2D(7/5, 12/5)]
        """
        # TODO: Replace solve with nonlinsolve, when nonlinsolve will be able to solve in real domain

        if isinstance(o, Point):
            if o in self:
                return [o]
            else:
                return []

        elif isinstance(o, (Segment2D, Ray2D)):
            hyperbola_equation = self.equation(x, y)
            result = solve([hyperbola_equation, Line(
                o.points[0], o.points[1]).equation(x, y)], [x, y],
                set=True)[1]
            return list(ordered([Point(i) for i in result if i in o]))

        elif isinstance(o, Polygon):
            return o.intersection(self)

        elif isinstance(o, (Ellipse,Parabola,Hyperbola, Line2D)):
            if o == self:
                return self
            else:
                hyperbola_equation = self.equation(x, y)
                return list(ordered([Point(i) for i in nonlinsolve(
                    [hyperbola_equation, o.equation(x, y)], [x, y])]))
        elif isinstance(o, LinearEntity3D):
            raise TypeError('Entity must be two dimensional, not three dimensional')
        else:
            raise TypeError('Intersection not handled for %s' % func_name(o))

    def is_tangent(self, o):
        """Is `o` tangent to the hyperbola?

        Parameters
        ==========

        o : GeometryEntity
            An Hyperbola, LinearEntity or Polygon

        Raises
        ======

        NotImplementedError
            When the wrong type of argument is supplied.

        Returns
        =======

        is_tangent: boolean
            True if o is tangent to the hyperbola, False otherwise.

        See Also
        ========

        tangent_lines

        Examples
        ========

        >>> from sympy import Point, Hyperbola, Line
        >>> p0, p1, p2 = Point(0, 0), Point(3, 0), Point(3, 3)
        >>> e1 = Hyperbola(p0, 3, 2)
        >>> l1 = Line(p1, p2)
        >>> e1.is_tangent(l1)
        True

        """
        if isinstance(o, Point2D):
            return False
        elif isinstance(o, Hyperbola):
            intersect = self.intersection(o)
            if isinstance(intersect, Hyperbola):
                return True
            elif intersect:
                return all((self.tangent_lines(i)[0]).equals(o.tangent_lines(i)[0]) for i in intersect)
            else:
                return False
        elif isinstance(o, Line2D):
            hit = self.intersection(o)
            if not hit:
                return False
            if len(hit) == 1:
                return True
            # might return None if it can't decide
            return hit[0].equals(hit[1])
        elif isinstance(o, Ray2D):
            intersect = self.intersection(o)
            if len(intersect) == 1:
                return intersect[0] != o.source and not self.encloses_point(o.source)
            else:
                return False
        elif isinstance(o, (Segment2D, Polygon)):
            all_tangents = False
            segments = o.sides if isinstance(o, Polygon) else [o]
            for segment in segments:
                intersect = self.intersection(segment)
                if len(intersect) == 1:
                    if not any(intersect[0] in i for i in segment.points) \
                        and not any(self.encloses_point(i) for i in segment.points):
                        all_tangents = True
                        continue
                    else:
                        return False
                else:
                    return all_tangents
            return all_tangents
        elif isinstance(o, (LinearEntity3D, Point3D)):
            raise TypeError('Entity must be two dimensional, not three dimensional')
        else:
            raise TypeError('Is_tangent not handled for %s' % func_name(o))

    def normal_lines(self, p, prec=None):
        """Normal lines between `p` and the hyperbola.

        Parameters
        ==========

        p : Point

        Returns
        =======

        normal_lines : list with 1, 2 or 4 Lines

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> e = Hyperbola((0, 0), 2, 3)
        >>> c = e.center
        >>> e.normal_lines(c + Point(1, 0))
        [Line2D(Point2D(0, 0), Point2D(1, 0))]
        >>> e.normal_lines(c)
        [Line2D(Point2D(0, 0), Point2D(0, 1)), Line2D(Point2D(0, 0), Point2D(1, 0))]

        Off-axis points require the solution of a quartic equation. This
        often leads to very large expressions that may be of little practical
        use. An approximate solution of `prec` digits can be obtained by
        passing in the desired value:

        >>> e.normal_lines((3, 3), prec=2)
        [Line2D(Point2D(-0.81, -2.7), Point2D(0.19, -1.2)),
        Line2D(Point2D(1.5, -2.0), Point2D(2.5, -2.7))]

        Whereas the above solution has an operation count of 12, the exact
        solution has an operation count of 2020.
        """
        p = Point(p, dim=2)

        # XXX change True to something like self.angle == 0 if the arbitrarily
        # rotated hyperbola is introduced.
        # https://github.com/sympy/sympy/issues/2815)
        if True:
            rv = []
            if p.y == self.center.y:
                rv.append(Line(self.center, slope=self.slope))
            if rv:
                # at these special orientations of p either 1 or 2 normals
                # exist and we are done
                return rv

        # find the 4 normal points and construct lines through them with
        # the corresponding slope
        eq = self.equation(x, y)
        dydx = idiff(eq, y, x)
        norm = -1/dydx
        slope = Line(p, (x, y)).slope
        seq = slope - norm

        # TODO: Replace solve with solveset, when this line is tested
        yis = solve(seq, y)[0]
        xeq = eq.subs(y, yis).as_numer_denom()[0].expand()
        if len(xeq.free_symbols) == 1:
            try:
                # this is so much faster, it's worth a try
                xsol = Poly(xeq, x).real_roots()
            except (DomainError, PolynomialError, NotImplementedError):
                # TODO: Replace solve with solveset, when these lines are tested
                xsol = _nsort(solve(xeq, x), separated=True)[0]
            points = [Point(i, solve(eq.subs(x, i), y)[0]) for i in xsol]
        else:
            raise NotImplementedError(
                'intersections for the general hyperbola are not supported')
        slopes = [norm.subs(zip((x, y), pt.args)) for pt in points]
        if prec is not None:
            points = [pt.n(prec) for pt in points]
            slopes = [i if _not_a_coeff(i) else i.n(prec) for i in slopes]
        return [Line(pt, slope=s) for pt, s in zip(points, slopes)]

    @property
    def periapsis(self):
        """The periapsis of the hyperbola.

        The shortest distance between the focus and the lear contour.

        Returns
        =======

        periapsis : number

        See Also
        ========

        apoapsis : Returns shottest distance between focus and the longer  contour

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.periapsis
        3 - sqrt(10)

        """
        return self.real * (1 - self.eccentricity)

    @property
    def semilatus_rectum(self):
        """
        Calculates the semi-latus rectum of the Hyperbola.

        Semi-latus rectum is defined as one half of the chord through a
        focus parallel to the conic section directrix of a conic section.

        Returns
        =======

        semilatus_rectum : number

        See Also
        ========

        apoapsis : Returns greatest distance between focus and contour

        periapsis : The shortest distance between the focus and the contour

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.semilatus_rectum
        1/3

        References
        ==========

        .. [1] https://mathworld.wolfram.com/SemilatusRectum.html
        .. [2] https://en.wikipedia.org/wiki/Hyperbola#Semi-latus_rectum

        """
        return self.real * (self.eccentricity ** 2 - 1)
    
    @property
    def asymptote(self):
        """Returns a the two asymptote of the hyperbola in a list.

        Examples
        ========

        >>> from sympy import Hyperbola, Point, symbols
        >>> c = Point(1, 2)
        >>> Hyperbola(c, 8, 7).asymptote
        [Line2D(Point2D(1, 2), Point2D(-8, 7)), Line2D(Point2D(1, 2), Point2D(8, 7))]
        >>> a, b = symbols('a b')
        >>> Hyperbola(c, a, b).asymptote
        [Line2D(Point2D(1, 2), Point2D(-a, b)), Line2D(Point2D(1, 2), Point2D(a, b))]
        """
        i=self.imaginary
        r=self.real
        k=self.slope
        if(self.slope==oo or self.slope==-oo):
            P_p=Point(-i,r)
            P_n=Point(-i,-r)
        else:
            P_p=Point(r-k*i,k*r+i)
            P_n=Point(-r-k*i,-k*r+i)
        return [Line(self.center,P_n),Line(self.center,P_p)]

    def plot_interval(self, parameter='t'):
        """The plot interval for the default geometric plot of the Hyperbola.

        Parameters
        ==========

        parameter : str, optional
            Default value is 't'.

        Returns
        =======

        plot_interval : list
            [parameter, lower_bound, upper_bound]

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> e1 = Hyperbola(Point(0, 0), 3, 2)
        >>> e1.plot_interval()
        [t, -pi, pi]

        """
        t = _symbol(parameter, real=True)
        return [t, -S.Pi, S.Pi]

    def random_point(self, seed=None):
        """A random point on the hyperbola.

        Returns
        =======

        point : Point

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> e1 = Hyperbola(Point(0, 0), 2, 3)
        >>> e1.random_point() # gives some random point
        Point2D(...)
        >>> p1 = e1.random_point(seed=0); p1.n(2)
        Point2D(2.9, -3.2)

        Notes
        =====

        When creating a random point, one may simply replace the
        parameter with a random number. When doing so, however, the
        random number should be made a Rational or else the point
        may not test as being in the hyperbola:

        >>> from sympy.abc import t
        >>> from sympy import Rational
        >>> arb = e1.arbitrary_point(t); arb
        Point2D(3*cos(t), 2*sin(t))
        >>> arb.subs(t, .1) in e1
        False
        >>> arb.subs(t, Rational(.1)) in e1
        True
        >>> arb.subs(t, Rational('.1')) in e1
        True

        See Also
        ========
        sympy.geometry.point.Point
        arbitrary_point : Returns parameterized point on hyperbola
        """
        t = _symbol('t', real=True)
        x, y = self.arbitrary_point(t).args
        # get a random value in [-1, 1) corresponding to cos(t)
        # and confirm that it will test as being in the hyperbola
        if seed is not None:
            rng = random.Random(seed)
        else:
            rng = random
        # simplify this now or else the Float will turn s into a Float
        r = Rational(rng.random())
        c = 2*r - 1
        s = sin(pi/2+pi*rng.randint(0,1))*sqrt(1 - c**2)/c
        sub_data={cos(t):c,tan(t):s}
        return Point(x.subs(sub_data), y.subs(sub_data))

    def reflect(self, line):
        """Override GeometryEntity.reflect since the radius
        is not a GeometryEntity.

        Examples
        ========

        >>> from sympy import Circle, Line
        >>> Circle((0, 1), 1).reflect(Line((0, 0), (1, 1)))
        Circle(Point2D(1, 0), -1)
        >>> from sympy import Hyperbola, Line, Point
        >>> Hyperbola(Point(3, 4), 1, 3).reflect(Line(Point(0, -4), Point(5, 0)))
        Traceback (most recent call last):
        ...
        NotImplementedError:
        General Hyperbola is not supported but the equation of the reflected
        Hyperbola is given by the zeros of: f(x, y) = (9*x/41 + 40*y/41 +
        37/41)**2 + (40*x/123 - 3*y/41 - 364/123)**2 - 1

        Notes
        =====

        Until the general hyperbola (with no axis parallel to the x-axis) is
        supported a NotImplemented error is raised and the equation whose
        zeros define the rotated hyperbola is given.

        """

        if line.slope in (0, oo):
            c = self.center
            c = c.reflect(line)
            return self.func(c, -self.real, self.imaginary)
        else:
            x, y = [uniquely_named_symbol(
                name, (self, line), modify=lambda s: '_' + s, real=True)
                for name in 'xy']
            expr = self.equation(x, y)
            p = Point(x, y).reflect(line)
            result = expr.subs(zip((x, y), p.args
                                   ), simultaneous=True)
            raise NotImplementedError(filldedent(
                'General Hyperbola is not supported but the equation '
                'of the reflected Hyperbola is given by the zeros of: ' +
                "f(%s, %s) = %s" % (str(x), str(y), str(result))))

    def rotate(self, angle=0, pt=None):
        """Rotate ``angle`` radians counterclockwise about Point ``pt``.

        Note: since the general hyperbola is not supported, only rotations that
        are integer multiples of pi/2 are allowed.

        Examples
        ========

        >>> from sympy import Hyperbola, pi
        >>> Hyperbola((1, 0), 2, 1).rotate(pi/2)
        Hyperbola(Point2D(0, 1), 1, 2)
        >>> Hyperbola((1, 0), 2, 1).rotate(pi)
        Hyperbola(Point2D(-1, 0), 2, 1)
        """
        if self.real == self.imaginary:
            return self.func(self.center.rotate(angle, pt), self.real)
        if (angle/S.Pi).is_integer:
            return super().rotate(angle, pt)
        if (2*angle/S.Pi).is_integer:
            return self.func(self.center.rotate(angle, pt), self.imaginary, self.real)
        # XXX see https://github.com/sympy/sympy/issues/2815 for general ellipes
        raise NotImplementedError('Only rotations of pi/2 are currently supported for Hyperbola.')

    def scale(self, x=1, y=1, pt=None):
        """Override GeometryEntity.scale since it is the real and imaginary
        axes which must be scaled and they are not GeometryEntities.

        Examples
        ========

        >>> from sympy import Hyperbola
        >>> Hyperbola((0, 0), 2, 1).scale(2, 4)
        Circle(Point2D(0, 0), 4)
        >>> Hyperbola((0, 0), 2, 1).scale(2)
        Hyperbola(Point2D(0, 0), 4, 1)
        """
        c = self.center
        if pt:
            pt = Point(pt, dim=2)
            return self.translate(*(-pt).args).scale(x, y).translate(*pt.args)
        h = self.real
        v = self.imaginary
        return self.func(c.scale(x, y), real=h*x, imaginary=v*y)

    def tangent_lines(self, p):
        """Tangent lines between `p` and the hyperbola.

        If `p` is on the hyperbola, returns the tangent line through point `p`.
        Otherwise, returns the tangent line(s) from `p` to the hyperbola, or
        None if no tangent line is possible (e.g., `p` inside hyperbola).

        Parameters
        ==========

        p : Point

        Returns
        =======

        tangent_lines : list with 1 or 2 Lines

        Raises
        ======

        NotImplementedError
            Can only find tangent lines for a point, `p`, on the hyperbola.

        See Also
        ========

        sympy.geometry.point.Point, sympy.geometry.line.Line

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> e1 = Hyperbola(Point(0, 0), 3, 2)
        >>> e1.tangent_lines(Point(3, 0))
        [Line2D(Point2D(3, 0), Point2D(3, -12))]

        """
        p = Point(p, dim=2)
        if self.encloses_point(p):
            return []

        if p in self:
            delta = self.center - p
            rise = (self.imaginary**2)*delta.x
            run = -(self.real**2)*delta.y
            p2 = Point(simplify(p.x + run),
                       simplify(p.y + rise))
            return [Line(p, p2)]
        else:
            if len(self.foci) == 2:
                f1, f2 = self.foci
                maj = self.real
                test = (2*maj -
                        Point.distance(f1, p) -
                        Point.distance(f2, p))
            else:
                test = self.radius - Point.distance(self.center, p)
            if test.is_number and test.is_positive:
                return []
            # else p is outside the hyperbola or we can't tell. In case of the
            # latter, the solutions returned will only be valid if
            # the point is not inside the hyperbola; if it is, nan will result.
            eq = self.equation(x, y)
            dydx = idiff(eq, y, x)
            slope = Line(p, Point(x, y)).slope

            # TODO: Replace solve with solveset, when this line is tested
            tangent_points = solve([slope - dydx, eq], [x, y])

            # handle horizontal and vertical tangent lines
            if len(tangent_points) == 1:
                if tangent_points[0][
                           0] == p.x or tangent_points[0][1] == p.y:
                    return [Line(p, p + Point(1, 0)), Line(p, p + Point(0, 1))]
                else:
                    return [Line(p, p + Point(0, 1)), Line(p, tangent_points[0])]

            # others
            return [Line(p, tangent_points[0]), Line(p, tangent_points[1])]

    @property
    def imaginary(self):
        """The vertical radius of the hyperbola.

        Returns
        =======

        imaginary : number

        See Also
        ========

        real, imaginary

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.imaginary
        1

        """
        return self.args[2]

    @property
    def slope(self):
        """Returns the tangent of a angle which is real axis and x-axis.

        Returns
        =======

        slope : number

        Examples
        ========

        >>> O=Point(0,0)
        >>> para=Parabola(O,3,4)
        >>> para.slope
        0
        >>> para=Parabola(O,3,3,1)
        >>> expand(para.equation())
        x*y - 9/2
        >>> para.slope
        1

        """
        return self.args[3]

    def second_moment_of_area(self, point=None):
        """Returns the second moment and product moment area of an hyperbola.

        Parameters
        ==========

        point : Point, two-tuple of sympifiable objects, or None(default=None)
            point is the point about which second moment of area is to be found.
            If "point=None" it will be calculated about the axis passing through the
            centroid of the hyperbola.

        Returns
        =======

        I_xx, I_yy, I_xy : number or SymPy expression
            I_xx, I_yy are second moment of area of an ellise.
            I_xy is product moment of area of an hyperbola.

        Examples
        ========

        >>> from sympy import Point, Hyperbola
        >>> p1 = Point(0, 0)
        >>> e1 = Hyperbola(p1, 3, 1)
        >>> e1.second_moment_of_area()
        (3*pi/4, 27*pi/4, 0)

        References
        ==========

        .. [1] https://en.wikipedia.org/wiki/List_of_second_moments_of_area

        """

        I_xx = (S.Pi*(self.real)*(self.imaginary**3))/4
        I_yy = (S.Pi*(self.real**3)*(self.imaginary))/4
        I_xy = 0

        if point is None:
            return I_xx, I_yy, I_xy

        # parallel axis theorem
        I_xx = I_xx + self.area*((point[1] - self.center.y)**2)
        I_yy = I_yy + self.area*((point[0] - self.center.x)**2)
        I_xy = I_xy + self.area*(point[0] - self.center.x)*(point[1] - self.center.y)

        return I_xx, I_yy, I_xy


    def polar_second_moment_of_area(self):
        """Returns the polar second moment of area of an Hyperbola

        It is a constituent of the second moment of area, linked through
        the perpendicular axis theorem. While the planar second moment of
        area describes an object's resistance to deflection (bending) when
        subjected to a force applied to a plane parallel to the central
        axis, the polar second moment of area describes an object's
        resistance to deflection when subjected to a moment applied in a
        plane perpendicular to the object's central axis (i.e. parallel to
        the cross-section)

        Examples
        ========

        >>> from sympy import symbols, Circle, Hyperbola
        >>> c = Circle((5, 5), 4)
        >>> c.polar_second_moment_of_area()
        128*pi
        >>> a, b = symbols('a, b')
        >>> e = Hyperbola((0, 0), a, b)
        >>> e.polar_second_moment_of_area()
        pi*a**3*b/4 + pi*a*b**3/4

        References
        ==========

        .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia

        """
        second_moment = self.second_moment_of_area()
        return second_moment[0] + second_moment[1]


    def section_modulus(self, point=None):
        """Returns a tuple with the section modulus of an hyperbola

        Section modulus is a geometric property of an hyperbola defined as the
        ratio of second moment of area to the distance of the extreme end of
        the hyperbola from the centroidal axis.

        Parameters
        ==========

        point : Point, two-tuple of sympifyable objects, or None(default=None)
            point is the point at which section modulus is to be found.
            If "point=None" section modulus will be calculated for the
            point farthest from the centroidal axis of the hyperbola.

        Returns
        =======

        S_x, S_y: numbers or SymPy expressions
                  S_x is the section modulus with respect to the x-axis
                  S_y is the section modulus with respect to the y-axis
                  A negative sign indicates that the section modulus is
                  determined for a point below the centroidal axis.

        Examples
        ========

        >>> from sympy import Symbol, Hyperbola, Circle, Point2D
        >>> d = Symbol('d', positive=True)
        >>> c = Circle((0, 0), d/2)
        >>> c.section_modulus()
        (pi*d**3/32, pi*d**3/32)
        >>> e = Hyperbola(Point2D(0, 0), 2, 4)
        >>> e.section_modulus()
        (8*pi, 4*pi)
        >>> e.section_modulus((2, 2))
        (16*pi, 4*pi)

        References
        ==========

        .. [1] https://en.wikipedia.org/wiki/Section_modulus

        """
        x_c, y_c = self.center
        if point is None:
            # taking x and y as maximum distances from centroid
            x_min, y_min, x_max, y_max = self.bounds
            y = max(y_c - y_min, y_max - y_c)
            x = max(x_c - x_min, x_max - x_c)
        else:
            # taking x and y as distances of the given point from the center
            point = Point2D(point)
            y = point.y - y_c
            x = point.x - x_c

        second_moment = self.second_moment_of_area()
        S_x = second_moment[0]/y
        S_y = second_moment[1]/x

        return S_x, S_y